#include "CentralCach.h"

CentralCach CentralCach::sInst_;

//获取一定内存给 thread cach
size_t CentralCach::FenchRangeObj(void*& start, void*& end, size_t batchNum, size_t size) {
    size_t index = SizeMapClass::Index(size);//计算桶
    spanList_[index].Lock();//给spanList_桶上锁

    Span* span = GetOneSpan(spanList_[index], size);//获取一个span节点
    assert(span);
    assert(span->freelist_);

    //分配span中的freelist_
    //如果不够batchNum个, 有几个分配几个
    start = span->freelist_;
    end = start;
    int actualNum = 1;//返回实际分配了几个内存块
    for (int i = 0; i < batchNum - 1 && NextPtr(end); ++i) {
        end = NextPtr(end);
        ++actualNum;
    }
    span->freelist_  = NextPtr(end);
    span->useCount_ += actualNum;//切出去几块
    NextPtr(end) = nullptr;
    spanList_[index].UnLock();//给spanList_解锁
    return actualNum;
}

//获取一个非空的span
Span* CentralCach::GetOneSpan(SpanList& list, size_t size) {
    //查看当前spanlist中freelist是否还有没分配完的span
    Span* it = list.Begin();
    while (it != list.End()) {
        if (it->freelist_ != nullptr) {
            return it;
        }
        it = it->next_;
    }

    list.UnLock();//先给spanList_解锁,因为接下来要去page cach要span,可以让其他线程来访问这个桶
    //CentralCash没有span,或span分配完了, 找page cach要
    PageCach::GetInstance()->Lock();
    Span* span = PageCach::GetInstance()->NewSpan(SizeMapClass::NumMovePage(size));
    span->isUse_ = true;//这个span已经被centralCach使用了
    span->objSize_ = size;//保存当前span每个内存块的大小
    PageCach::GetInstance()->UnLock();

    //切分不需要加锁,这是某个线程独有的span,链入Central Cach 的spanList桶才加锁
    //计算span的大块内存起始地址和字节数
    char* start  = (char*)(span->pageId_ * PAGE_SIZE);
    size_t bytes = span->n_ * PAGE_SIZE;
    char*  end   = start + bytes;
    span->freelist_ = start;
    //把span的大块内存切成小块挂在自由链表上 
    while (start + size < end) {
        NextPtr(start) = start + size;
        start += size;
    }
    NextPtr(start) = nullptr;//确保最后一块指向空

    //插入桶需要加锁
    list.Lock();
    list.Push(span);//把span插入CentralCach spanlist中 (头插)
    return span;
}

//threaad cach 还一段零碎的内存块给cnetral cach , 大小都是size
void CentralCach::ReleaseListToCentralCach(void* start, size_t size) {
    assert(start);
    assert(size > 0);
    size_t index = SizeMapClass::Index(size);
    spanList_[index].Lock();
    while (start)
    {
        //根据哈希映射找这段内存块属于那个span, 根据地址 ---> 页号 ----> span
        void* next = NextPtr(start);
        Span* span = PageCach::GetInstance()->MapObjectToSpan(start);//访问哈希表得加锁,否则线程不安全
        NextPtr(start)  = span->freelist_;
        span->freelist_ = start;
        if (--span->useCount_ == 0) {//还回来一块,等于0说明整个span的所有页都被treadCach释放,归还给pageCach
            spanList_[index].Erase(span);//这个span节点先从CentralCach断开
            spanList_[index].UnLock();//先解桶锁,这儿已经跟centralCach没关系了,接下来要还给pagecach

            PageCach::GetInstance()->Lock();
            PageCach::GetInstance()->ReleaseSpanToPageCach(span);
            PageCach::GetInstance()->UnLock();

            spanList_[index].Lock();//又加上锁,这儿还给pageCach后又回来了,要继续访问这个桶
        }
        start = next;
    }

    spanList_[index].UnLock();
}